package io.github.eric.urpc.client;

import io.github.eric.urpc.core.config.AbstractConfig;
import io.github.eric.urpc.core.config.Config;
import io.github.eric.urpc.core.conn.ConnectionListener;
import io.github.eric.urpc.core.rpc.RpcController;
import io.github.eric.urpc.core.thread.NamePrefixThreadFactory;
import io.github.eric.urpc.core.thread.OrderedThreadPoolExecutor;
import io.netty.handler.logging.LogLevel;
import io.netty.util.HashedWheelTimer;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public interface ClientConfig extends Config {
    String getServerHost();
    int getServerPort();
    int getHeartBeatInterval();
    int getSocketConnectTimeout();
    int getConnectionInitTimeout();
    int getConnectionPoolMaxTotal();
    int getConnectionPoolMaxIdle();
    int getConnectionPoolMinIdle();
    int getConnectionPoolTimeBetweenEvictionRuns();
    int getConnectionPoolMaxWait();

    class Builder {
        private static final String PROP_SOCKET_CONNECT_TIMEOUT = "urpc.client.socketConnectTimeout";
        private static final String PROP_SERVER_HOST = "urpc.client.serverHost";
        private static final String PROP_SERVER_PORT = "urpc.client.serverPort";
        private static final String PROP_RPC_EXECUTOR_CORE_SIZE = "urpc.client.rpcExecutorCoreSize";
        private static final String PROP_RPC_EXECUTOR_QUEUE_SIZE = "urpc.client.rpcExecutorQueueSize";
        private static final String PROP_HEART_BEAT_INTERVAL = "urpc.client.heartBeatInterval";
        private static final String PROP_CONNECTION_INIT_TIMEOUT = "urpc.client.connectionInitTimeout";
        private static final String PROP_CONNECTION_POOL_MAX_TOTAL = "urpc.client.connectionPoolMaxTotal";
        private static final String PROP_CONNECTION_POOL_MAX_IDLE = "urpc.client.connectionPoolMaxIdle";
        private static final String PROP_CONNECTION_POOL_MIN_IDLE = "urpc.client.connectionPoolMinIdle";
        private static final String PROP_CONNECTION_POOL_TIME_BETWEEN_EVICTION_RUNS = "urpc.client.connectionPoolTimeBetweenEvictionRuns";
        private static final String PROP_CONNECTION_POOL_MAX_WAIT = "urpc.client.connectionPoolMaxWait";

        private String applicationName;
        private String applicationId;

        private int socketBacklog;
        private int socketRecvBufSize;
        private int socketSendBufSize;
        private int frameMaxLength;
        private int socketConnectTimeout;

        private String serverHost;
        private int serverPort;

        private List<RpcController> rpcControllers;

        private ExecutorService rpcExecutor;
        private int rpcExecutorThreadSize;
        private int rpcExecutorQueueSize;

        private int heartBeatInterval;

        private LogLevel nettyLoggingLevel;

        private int readTimeout;
        private int connectionInitTimeout;

        private int connectionPoolMaxTotal;
        private int connectionPoolMaxIdle;
        private int connectionPoolMinIdle;
        private int connectionPoolTimeBetweenEvictionRuns;
        private int connectionPoolMaxWait;

        private boolean ordered;

        private List<ConnectionListener> connectionListeners;

        public Builder() {
            int cpuCore = Runtime.getRuntime().availableProcessors();

            this.applicationId = System.getProperty(AbstractConfig.PROP_APPLICATION_ID, AbstractConfig.DEFAULT_CURRENT_APPLICATION_ID);
            this.socketBacklog = Integer.getInteger(AbstractConfig.PROP_SOCKET_BACKLOG, AbstractConfig.DEFAULT_SOCKET_BACKLOG);
            this.socketRecvBufSize = Integer.getInteger(AbstractConfig.PROP_SOCKET_RECV_BUF_SIZE, AbstractConfig.DEFAULT_SOCKET_RECV_BUF_SIZE);
            this.socketSendBufSize = Integer.getInteger(AbstractConfig.PROP_SOCKET_SEND_BUF_SIZE, AbstractConfig.DEFAULT_SOCKET_SEND_BUF_SIZE);
            this.frameMaxLength = Integer.getInteger(AbstractConfig.PROP_FRAME_MAX_LENGTH, AbstractConfig.DEFAULT_FRAME_MAX_LENGTH);
            this.socketConnectTimeout = Integer.getInteger(PROP_SOCKET_CONNECT_TIMEOUT, 2);
            this.serverHost = System.getProperty(PROP_SERVER_HOST, "localhost");
            this.serverPort = Integer.getInteger(PROP_SERVER_PORT, 52520);
            this.rpcExecutorThreadSize = Integer.getInteger(PROP_RPC_EXECUTOR_CORE_SIZE, Math.max(cpuCore+1, 1));
            this.rpcExecutorQueueSize = Integer.getInteger(PROP_RPC_EXECUTOR_QUEUE_SIZE, 128);
            this.heartBeatInterval = Integer.getInteger(PROP_HEART_BEAT_INTERVAL, 10);
            try {
                this.nettyLoggingLevel = LogLevel.valueOf(System.getProperty(AbstractConfig.PROP_NETTY_LOGGING_LEVEL));
            } catch (Exception ignore) {
                this.nettyLoggingLevel = null;
            }
            this.readTimeout = Integer.getInteger(AbstractConfig.PROP_READ_TIMEOUT, AbstractConfig.DEFAULT_READ_TIMEOUT);
            this.connectionInitTimeout = Integer.getInteger(PROP_CONNECTION_INIT_TIMEOUT, 3);
            this.connectionPoolMaxTotal = Integer.getInteger(PROP_CONNECTION_POOL_MAX_TOTAL, 1);
            this.connectionPoolMaxIdle = Integer.getInteger(PROP_CONNECTION_POOL_MAX_IDLE, 1);
            this.connectionPoolMinIdle = Integer.getInteger(PROP_CONNECTION_POOL_MIN_IDLE, 0);
            this.connectionPoolTimeBetweenEvictionRuns = Integer.getInteger(PROP_CONNECTION_POOL_TIME_BETWEEN_EVICTION_RUNS, 60);
            this.connectionPoolMaxWait = Integer.getInteger(PROP_CONNECTION_POOL_MAX_WAIT, 3);
            this.ordered = Boolean.getBoolean(AbstractConfig.PROP_ORDERED);
        }

        public Builder socketBacklog(int socketBacklog) {
            this.socketBacklog = socketBacklog;
            return this;
        }

        public Builder socketRecvBufSize(int socketRecvBufSize) {
            this.socketRecvBufSize = socketRecvBufSize;
            return this;
        }

        public Builder socketSendBufSize(int socketSendBufSize) {
            this.socketSendBufSize = socketSendBufSize;
            return this;
        }

        public Builder frameMaxLength(int frameMaxLength) {
            this.frameMaxLength = frameMaxLength;
            return this;
        }

        public Builder serverHost(String serverHost) {
            this.serverHost = serverHost;
            return this;
        }

        public Builder serverPort(int serverPort) {
            this.serverPort = serverPort;
            return this;
        }

        public Builder rpcControllers(List<RpcController> rpcControllers) {
            this.rpcControllers = rpcControllers;
            return this;
        }

        public Builder rpcExecutor(ExecutorService rpcExecutor) {
            this.rpcExecutor = rpcExecutor;
            return this;
        }

        public Builder rpcExecutorThreadSize(int rpcExecutorThreadSize) {
            this.rpcExecutorThreadSize = rpcExecutorThreadSize;
            return this;
        }

        public Builder rpcExecutorQueueSize(int rpcExecutorQueueSize) {
            this.rpcExecutorQueueSize = rpcExecutorQueueSize;
            return this;
        }

        /**
         * @param heartBeatInterval [unit: second]
         * @return
         */
        public Builder heartBeatInterval(int heartBeatInterval) {
            this.heartBeatInterval = heartBeatInterval;
            return this;
        }

        public Builder nettyLoggingLevel(LogLevel nettyLoggingLevel) {
            this.nettyLoggingLevel = nettyLoggingLevel;
            return this;
        }

        public Builder socketConnectTimeout(int connectTimeout) {
            this.socketConnectTimeout = connectTimeout;
            return this;
        }

        public Builder readTimeout(int readTimeout) {
            this.readTimeout = readTimeout;
            return this;
        }

        public Builder connectionInitTimeout(int connectionInitTimeout) {
            this.connectionInitTimeout = connectionInitTimeout;
            return this;
        }

        public Builder applicationName(String applicationName) {
            this.applicationName = applicationName;
            return this;
        }

        public Builder applicationId(String applicationId) {
            this.applicationId = applicationId;
            return this;
        }

        public Builder connectionPoolMaxTotal(int connectionPoolMaxTotal) {
            this.connectionPoolMaxTotal = connectionPoolMaxTotal;
            return this;
        }

        public Builder connectionPoolMaxIdle(int connectionPoolMaxIdle) {
            this.connectionPoolMaxIdle = connectionPoolMaxIdle;
            return this;
        }

        public Builder connectionPoolMinIdle(int connectionPoolMinIdle) {
            this.connectionPoolMinIdle = connectionPoolMinIdle;
            return this;
        }

        public Builder connectionPoolTimeBetweenEvictionRuns(int connectionPoolTimeBetweenEvictionRuns) {
            this.connectionPoolTimeBetweenEvictionRuns = connectionPoolTimeBetweenEvictionRuns;
            return this;
        }

        public Builder connectionPoolMaxWait(int connectionPoolMaxWait) {
            this.connectionPoolMaxWait = connectionPoolMaxWait;
            return this;
        }

        public Builder ordered(boolean ordered) {
            this.ordered = ordered;
            return this;
        }

        public Builder connectionListeners(List<ConnectionListener> connectionListeners) {
            this.connectionListeners = connectionListeners;
            return this;
        }

        public ClientConfig build() {
            if (this.applicationName == null || this.applicationName.isEmpty()) {
                throw new IllegalArgumentException("prop '" + AbstractConfig.PROP_APPLICATION_NAME + "' is not present");
            }
            if (this.rpcExecutor == null) {
                if (ordered) {
                    this.rpcExecutor = new OrderedThreadPoolExecutor(this.rpcExecutorThreadSize, this.rpcExecutorQueueSize,
                            "urpc-client-" + serverHost + ":" + serverPort + "-exec");
                } else {
                    this.rpcExecutor = new ThreadPoolExecutor(this.rpcExecutorThreadSize, this.rpcExecutorThreadSize,
                            0L, TimeUnit.SECONDS,
                            new LinkedBlockingDeque<>(this.rpcExecutorQueueSize),
                            new NamePrefixThreadFactory("urpc-client-" + serverHost + ":" + serverPort + "-exec"));
                }
            }

            ClientContext clientContext = new ClientContext(rpcExecutor);
            clientContext.setApplicationName(applicationName);
            clientContext.setApplicationId(applicationId);
            clientContext.setSocketBacklog(socketBacklog);
            clientContext.setSocketRecvBufSize(socketRecvBufSize);
            clientContext.setSocketSendBufSize(socketSendBufSize);
            clientContext.setFrameMaxLength(frameMaxLength);
            clientContext.setServerHost(serverHost);
            clientContext.setServerPort(serverPort);
            clientContext.addRpcControllers(rpcControllers);
            clientContext.addConnectionListeners(connectionListeners);
            clientContext.setHeartBeatInterval(heartBeatInterval);
            clientContext.setNettyLoggingLevel(nettyLoggingLevel);
            clientContext.setReadTimeout(readTimeout);
            clientContext.setSocketConnectTimeout(socketConnectTimeout);
            clientContext.setConnectionInitTimeout(connectionInitTimeout);
            clientContext.setConnectionPoolMaxTotal(connectionPoolMaxTotal);
            clientContext.setConnectionPoolMaxIdle(connectionPoolMaxIdle);
            clientContext.setConnectionPoolMinIdle(connectionPoolMinIdle);
            clientContext.setConnectionPoolTimeBetweenEvictionRuns(connectionPoolTimeBetweenEvictionRuns);
            clientContext.setConnectionPoolMaxWait(connectionPoolMaxWait);
            clientContext.setHashedWheelTimer(new HashedWheelTimer(new NamePrefixThreadFactory("urpc-client-"+serverHost+":"+serverPort+"-hashedWheelTimer")));
            return clientContext;
        }
    }
}
